""" Multinode_Algorithm.py: uses DAG_Reader.py to parse the sample xml file
    containing a DAG and returns the total cost of running on the cloud after
    running a cost cutting algorithm

    CR (cost rate) is the average cost per hour for a node Ni in the system
    CR = (Ni,size * COST_S) + (Ni,size * IO_RATE) if the node is stored
    OR
    CR = genCost(Ni) / (1 hour/reuse), if the node has to be recomputed

    genCost(Ni) is the cost of recomputing Ni and all its parent nodes that are
    not stored. The set of parent nodes that need to be recomputed for a particular
    node is denoted by Ni_pSet
    genCost(Ni) = (Ni,comp_time + SUM(Ni_pSet,comp_time)) * COST_C
"""

from DAG_Reader import DAGParser

ALGORITHM_5050 = 1
ALGORITHM_DYNAMIC = 0

DAG_FILE="dag_multi.xml"
# storage/re-computation costs from Amazon Cloud
COST_C = .085 # computation cost per hour
IO_RATE = .12 # per GB
COST_S = 0.000136895463 # (0.1 per GB / hours in a month)-storage cost per hour


# formula to calculate storage costs
def calc_storage_cost(dagGraph, node_ID):
    node = dagGraph.DAG[node_ID]
    return (float(node['storage']) * IO_RATE) + (float(node['storage']) * COST_S)

# formula to calculate re-computation costs
# takes into account re-computation cost of parent nodes
def calc_recomp_cost(dagGraph, node_ID):
    parent_recomp_rate = 0.0

    try:
        node = dagGraph.DAG[node_ID]
        for parent in node['prev']:
            if dagGraph.DAG[parent]['flag'] != '1': # not stored
                parent_recomp_rate += calc_recomp_cost(dagGraph, parent)

        node_recomp_rate = float(node['compUnits'])
        return ((parent_recomp_rate + node_recomp_rate) * COST_C) * float(node['reuse'])
    except KeyError:
        return 0

def find_start_nodes(dagGraph):
    start_nodes = []

    try:
        for node_ID in dagGraph.DAG:
            if '' in dagGraph.DAG[node_ID]['prev']:
                start_nodes.append(node_ID)
        return start_nodes
    except KeyError:
        return start_nodes

# multi-node cost optimization algorithm
def mcoa(dagGraph, node_ID):
    while node_ID:
        store_cost = calc_storage_cost(dagGraph, node_ID)
        comp_cost = calc_recomp_cost(dagGraph, node_ID)
        try:
            if store_cost < comp_cost:
                dagGraph.DAG[node_ID]['flag'] = 1
                dagGraph.DAG[node_ID]['CR'] = store_cost
            else:
                dagGraph.DAG[node_ID]['flag'] = 0
                dagGraph.DAG[node_ID]['CR'] = comp_cost
            node_ID = dagGraph.DAG[node_ID]['next']
        except KeyError:
            return -1


# create DAGParser object
dagGraph = DAGParser(DAG_FILE)
startNodes = find_start_nodes(dagGraph)

for node_ID in startNodes:
    # start nodes (inputs) are always stored
    dagGraph.DAG[node_ID]['flag'] = 1
    dagGraph.DAG[node_ID]['CR'] = calc_storage_cost(dagGraph, node_ID)
    child_node = dagGraph.DAG[node_ID]['next']
    mcoa(dagGraph, child_node)

    print ('Results:')
    d = node_ID
    total_rate = 0.0
    while d:
        rate = (dagGraph.DAG[d]['CR'] * 730.48)
        print ('Node ID:', d, ' Flag:', dagGraph.DAG[d]['flag'],
               ' Cost for a month:', rate)
        total_rate += rate
        d = dagGraph.DAG[d]['next']

    print ('Total Rate for a month', total_rate)
